﻿//
// WaterComponent.cs
//
// This file is part of the QuickStart Engine. See http://www.codeplex.com/QuickStartEngine
// for license details.
//

using System;
using System.Collections.Generic;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Alex_s_Dream_The_Toy_World
{
    public class WaterComponent : BaseComponent
    {
        /*
         * Water Input Structure:
         *   struct ADV_VS_INPUT
             {
                 float4 Position            : POSITION0;
                 float2 TextureCoords       : TEXCOORD0;
             };
         * */

        public struct VertexWater
        {
            public VertexWater(Vector3 pos, Vector2 texCoord)
            {
                Position = pos;
                TexCoords = texCoord;
            }

            public Vector3 Position;
            public Vector2 TexCoords;

            public static int SizeInBytes = (3 + 2) * sizeof(float);
            public static VertexElement[] VertexElements = new VertexElement[]
            {
                new VertexElement( 0, 0, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Position, 0 ),
                new VertexElement( 0, sizeof(float) * 3, VertexElementFormat.Vector2, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 0 )        
            };
        }

        // Vertex declaration describes the format of our ParticleVertex structure.
        static private VertexDeclaration vertexDeclaration;

        private VertexWater[] waterVertices;

        /// <summary>
        /// This water component's material
        /// </summary>
        private Material material;

        /// <summary>
        /// Last recorded position of the water plane, this is used to keep track of changes to update things like
        /// the graphics system and bounding box.
        /// </summary>
        private Vector3 lastPosition;

        /// <summary>
        /// Bounding box that surrounds the water plane, used for frustum culling.
        /// </summary>
        private BoundingBox boundingBox;

        /// <summary>
        /// Color of the water
        /// </summary>
        public Vector4 WaterColor
        {
            get { return this.waterColor; }
            set { this.waterColor = value; }
        }
        private Vector4 waterColor = new Vector4(0.0f, 0.1f, 0.2f, 1.0f);

        public float Reflectivity
        {
            get { return this.reflectivity; }
            set { this.reflectivity = MathHelper.Clamp(value, 0.0f, 1.0f); }
        }
        private float reflectivity = 0.5f;

        /// <summary>
        /// Sets the color of the water, which is a <see cref="Vector4"/>, from a <see cref="Color"/> value.
        /// </summary>
        /// <param name="waterColor">Color to set water to.</param>
        public void SetWaterColor(Color waterColor)
        {
            this.waterColor = waterColor.ToVector4();
        }

        public WaterComponent(BaseEntity parent, int width, int length, string materialPath) :
            base(parent)
        {
            ActivateComponent();

            SetupWaterVertices(width, length);

            // The vertex declaration is static, so we only need to create it once, and all water planes can use it
            if (vertexDeclaration == null)
            {
                vertexDeclaration = new VertexDeclaration(this.parentEntity.Game.GraphicsDevice, VertexWater.VertexElements);
            }

            this.material = this.parentEntity.Game.Content.Load<Material>(materialPath);

            switch (this.parentEntity.Game.Graphics.Settings.GraphicsLevel)
            {
                case GraphicsLevel.Highest:
                case GraphicsLevel.High:
                    this.material.CurrentTechnique = "Water";
                    break;
                case GraphicsLevel.Med:
                    this.material.CurrentTechnique = "WaterReflectOnly";
                    break;
                case GraphicsLevel.Low:
                    this.material.CurrentTechnique = "WaterReflectOnly";
                    break;
            }
            
        }

        /// <summary>
        /// Setup the two triangles that makeup the water plane.
        /// </summary>
        private void SetupWaterVertices(int width, int length)
        {
            this.waterVertices = new VertexWater[6];

            this.waterVertices[0] = new VertexWater(Vector3.Zero, new Vector2(0, 1));
            this.waterVertices[1] = new VertexWater(new Vector3(0, 0, length), new Vector2(0, 0));
            this.waterVertices[2] = new VertexWater(new Vector3(width, 0, 0), new Vector2(1, 0));

            this.waterVertices[3] = new VertexWater(new Vector3(width, 0, 0), new Vector2(0, 1));
            this.waterVertices[4] = new VertexWater(new Vector3(0, 0, length), new Vector2(1, 1));
            this.waterVertices[5] = new VertexWater(new Vector3(width, 0, length), new Vector2(1, 0));

            boundingBox.Min = this.waterVertices[0].Position;
            boundingBox.Min.Y -= 1;
            boundingBox.Max = this.waterVertices[5].Position;
            boundingBox.Max.Y += 1;

            // Let graphics system know about the new elevation
            MsgSetGraphicsWaterElevation msgSetElev = ObjectPool.Aquire<MsgSetGraphicsWaterElevation>();
            msgSetElev.Elevation = waterVertices[0].Position.Y;
            this.parentEntity.Game.SendInterfaceMessage(msgSetElev, InterfaceType.Graphics);
        }

        /// <summary>
        /// Used to update the two triangles that make up the water plane, they must be
        /// updated when their position changes.
        /// </summary>
        private void UpdateWaterVertices()
        {
            MsgGetPosition msgGetPos = ObjectPool.Aquire<MsgGetPosition>();
            msgGetPos.UniqueTarget = this.parentEntity.UniqueID;
            this.parentEntity.Game.SendMessage(msgGetPos);

            if (lastPosition != msgGetPos.Position)
            {
                Vector3 positionChange = msgGetPos.Position - lastPosition;

                lastPosition = msgGetPos.Position;

                for (int i = 0; i < waterVertices.Length; ++i)
                {
                    waterVertices[i].Position += positionChange;
                }

                // We need to update the bounding box as well
                boundingBox.Min.Y += positionChange.Y;
                boundingBox.Max.Y += positionChange.Y;

                // Let graphics system know about the new elevation
                MsgSetGraphicsWaterElevation msgSetElev = ObjectPool.Aquire<MsgSetGraphicsWaterElevation>();
                msgSetElev.Elevation = waterVertices[0].Position.Y;
                this.parentEntity.Game.SendInterfaceMessage(msgSetElev, InterfaceType.Graphics);
            }
        }

        /// <summary>
        /// Update the component
        /// </summary>
        /// <param name="gameTime">XNA Timing snapshot</param>
        public override void Update(GameTime gameTime)
        {
            UpdateWaterVertices();
        }        

        public override void QueryForChunks(ref RenderPassDesc desc)
        {
            if (desc.GeometryChunksOnlyThisPass)
                return;

            if (!desc.ViewFrustum.Intersects(this.boundingBox))
                return;

            WaterChunk chunk = this.parentEntity.Game.Graphics.AllocateWaterChunk();
            chunk.Vertices = this.waterVertices;
            chunk.Declaration = vertexDeclaration;
            chunk.Material = this.material;
            chunk.Type = PrimitiveType.TriangleList;
            chunk.PrimitiveCount = 2;   // The water plane is only two triangles.
            chunk.Elevation = this.waterVertices[0].Position.Y;
            chunk.Reflectivity = this.reflectivity;
            chunk.WaterColor = this.waterColor;
        }

        /// <summary>
        /// Handles a message sent to this component.
        /// </summary>
        /// <param name="message">Message to be handled</param>
        /// <returns>True, if handled, otherwise false</returns>
        public override bool ExecuteMessage(IMessage message)
        {
            if (message.UniqueTarget != this.parentEntity.UniqueID)
                return false;

            switch (message.Type)
            {
                case MessageType.SetWaterColor:
                    {
                        MsgSetWaterColor msgSetColor = message as MsgSetWaterColor;
                        message.TypeCheck(msgSetColor);

                        this.WaterColor = msgSetColor.WaterColor.ToVector4();
                    }
                    return true;
                case MessageType.SetWaterReflectivity:
                    {
                        MsgSetWaterReflectivity msgSetReflectivity = message as MsgSetWaterReflectivity;
                        message.TypeCheck(msgSetReflectivity);

                        this.reflectivity = msgSetReflectivity.Reflectivity;
                    }
                    return true;
                default:
                    return false;
            }

            return false;
        }
    }
}
